////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   ParticleManager.cpp
//  Version:     v1.00
//  Created:     28/5/2001 by Vladimir Kajalin
//  Compilers:   Visual Studio.NET
//  Description: manage particles and sprites
// -------------------------------------------------------------------------
//  History:
//	- 03:2006				 : Modified by Jan Mueller (Serialization)
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "ParticleManager.h"
#include "ParticleEmitter.h"
#include "ParticleMemory.h"

#include <StringUtils.h>
#include <IJobManager_JobDelegator.h>

#include <../CrySystem/md5.h>
#if !defined(_LIB) && !defined(CAFE)
#include <../CrySystem/md5.c>
#endif //_LIB

#define LIBRARY_PATH			"Libs/"
#define EFFECTS_SUBPATH		LIBRARY_PATH "Particles/"
#define LEVEL_PATH				"Levels/"

using namespace minigui;


// forward declaration of Function to update particles
void UpdateParticlesBatch( int seed, UpdateParticlesEmitterBatch batch );

// forward declaration of free function to sync compute vertices job queues
void SyncComputeVerticesJobQueues();

DECLARE_JOB("UpdateParticles", TUpdateParticlesJob, UpdateParticlesBatch );

class UpdateParticlesEmitterBatchWrapper
{
public:
	UpdateParticlesEmitterBatchWrapper() :
		m_nCounter(0), m_nCurQueue(0)
	{			
		ZeroStruct(m_Batch);
		m_pJobState = CParticleManager::Instance()->GetJobState();
		m_bUseSPUs = gEnv->pJobManager->InvokeAsJob("UpdateParticles");
	}

	~UpdateParticlesEmitterBatchWrapper()
	{
		// send the last updates to the SPU
		if (m_nCounter)
			StartJob();
	}

	void Add( CParticleEmitter *pEmitter )
	{				
		if (!m_bUseSPUs)
		{
			pEmitter->UpdateAllParticleContainerSpu();
			return;
		}

		// dispatch updateparticles batch to spu if batch is full
		IF (m_nCounter == UpdateParticlesEmitterBatch::MAX_BATCHES, 0)
			StartJob();
		pEmitter->SetUpdateParticlesJobState(m_pJobState);
		assert(m_nCounter >= 0 && m_nCounter < sizeof(m_Batch.batch) / sizeof(m_Batch.batch[0]));
		m_Batch.batch[m_nCounter++] = pEmitter;				
	}

private:

	void StartJob()
	{			
		TUpdateParticlesJob job( cry_rand(), m_Batch );					
		job.SetCacheMode(JobManager::SPUBackend::eCM_4);
		job.RegisterJobState(m_pJobState);
		job.Run();			
		ZeroStruct(m_Batch);
		m_nCounter = 0;
		m_pJobState = CParticleManager::Instance()->GetJobState();
	}

	UpdateParticlesEmitterBatch m_Batch;
	int m_nCounter;
	int m_nCurQueue;	
	JobManager::SJobState *m_pJobState;
	bool m_bUseSPUs;
};

//////////////////////////////////////////////////////////////////////////
struct CNullPartManager: IParticleManager
{
	virtual IParticleEffect* CreateEffect() { return 0; }
	virtual void DeleteEffect( IParticleEffect* pEffect ) {}
	virtual IParticleEffect* FindEffect( cstr sEffectName, cstr sSource = "", bool bLoadResources = true ) { return 0; }
	virtual IParticleEffect* LoadEffect( cstr sEffectName, XmlNodeRef& effectNode, bool bLoadResources, const cstr sSource = NULL ) { return 0; }
	virtual bool LoadLibrary( cstr sParticlesLibrary, XmlNodeRef& libNode, bool bLoadResources ) { return false; }
	virtual bool LoadLibrary( cstr sParticlesLibrary, cstr sParticlesLibraryFile = NULL, bool bLoadResources = false ) { return false; }

	virtual IParticleEmitter* CreateEmitter( const PivotLocation& loc, const ParticleParams& Params, uint32 uEmitterFlags = 0, const SpawnParams* pSpawnParams = NULL ) { return 0; }
	virtual void DeleteEmitter( IParticleEmitter * pPartEmitter ) {}
	virtual void DeleteEmitters( uint32 mask ) {}
	IParticleEmitter* SerializeEmitter( TSerialize ser, IParticleEmitter* pEmitter = NULL ) { return NULL; }

	virtual void Update() {}
	virtual void RenderDebugInfo() {}
	virtual void Reset( bool bIndependentOnly ) {}
	virtual void ClearRenderResources( bool bForceClear ) {}
	virtual void OnStartRuntime() {}
	virtual void OnFrameStart() {}
	virtual void Serialize( TSerialize ser ) {}
	virtual void PostSerialize( bool bReading ) {}

	virtual void GetMemoryUsage( ICrySizer* pSizer ) const {}
	virtual void GetCounts( SParticleCounts& counts ) {}
	virtual void CreatePerfHUDWidget() {}

	virtual void AddEventListener(IParticleEffectListener *pListener) {}
	virtual void RemoveEventListener(IParticleEffectListener *pListener) {}

	virtual void SetTimer(ITimer* pTimer) {}

	virtual void PrepareForRender() {}
	virtual void FinishRender() {}
	
	virtual void AddFrameTicks( uint64 nTicks ){}
	virtual void ResetFrameTicks() {}
	virtual uint64 NumFrameTicks() const { return 0;}	
	virtual uint32 NumEmitter() const { return 0; }	
	virtual void AddFrameSyncTicks( uint64 nTicks ) {}
	virtual uint64 NumFrameSyncTicks() const { return 0; }

	virtual void SyncComputeVerticesJobQueues() {}
};

//////////////////////////////////////////////////////////////////////////
// Single direct entry point for particle clients.
IParticleManager* CreateParticleManager(bool bEnable)
{
	if (bEnable)
		return new CParticleManager();
	else
		return new CNullPartManager();
}

//////////////////////////////////////////////////////////////////////////
ITimer* g_pParticleTimer = NULL;

//////////////////////////////////////////////////////////////////////////
CParticleManager::CParticleManager()
{
	m_bRuntime = false;
	m_bRegisteredListener = false;
#ifdef bEVENT_TIMINGS
	m_iEventSwitch = 1;
	m_timeThreshold = 0.f;
#endif
	m_pWidget = NULL;
	g_pParticleTimer = gEnv->pTimer;

	if (GetPhysicalWorld())
	{
		GetPhysicalWorld()->AddEventClient(EventPhysAreaChange::id, &StaticOnPhysAreaChange, 0);

		REGISTER_COMMAND("e_ParticleListEmitters", &CmdParticleListEmitters, 0, "Writes all emitters to log");
		REGISTER_COMMAND("e_ParticleListEffects", &CmdParticleListEffects, 0, "Writes all effects used and counts to log");
		REGISTER_COMMAND("e_ParticleMemory", &CmdParticleMemory, 0, "Displays current particle memory usage");
	}

	m_nFrameTicks = 0;
	m_nFrameSyncTicks = 0;
	m_nActiveEmitter = 0;

	UpdateEngineData();
}

CParticleManager::~CParticleManager() 
{
	if (Get3DEngine()->GetIVisAreaManager())
	{
		Get3DEngine()->GetIVisAreaManager()->RemoveListener(this);
	}
	GetPhysicalWorld()->RemoveEventClient(EventPhysAreaChange::id, &StaticOnPhysAreaChange, 0);
	Destroy(false);
	
	// Destroy references to shaders.
	m_pPartLightShader = NULL;

	if (m_pWidget)
	{
		gEnv->pSystem->GetPerfHUD()->RemoveWidget(m_pWidget);
	}
}

struct SortEffectStats
{
	typedef CParticleManager::TEffectStats::value_type SEffectStatEntry;

	float SParticleCounts::* _pSortField;

	SortEffectStats( float SParticleCounts::* pSortField )
		: _pSortField(pSortField) {}

	bool operator()(const SEffectStatEntry& a, const SEffectStatEntry& b) const
	{
		return a.second.*_pSortField > b.second.*_pSortField;
	}
};

void CParticleManager::CollectEffectStats( TEffectStats& mapEffectStats, float SParticleCounts::* pSortField ) const
{
	SParticleCounts countsTotal;
	for_all_ptrs (const CParticleEmitter, e, m_Emitters)
	{
		for_all_ptrs (const CParticleContainer, c, e->GetContainers())
		{
			SParticleCounts countsEmitter;
			c->GetCounts(countsEmitter);
			SParticleCounts& countsEffect = mapEffectStats[c->GetEffect()];
			AddArray( FloatArray(countsEffect), FloatArray(countsEmitter) );
			AddArray( FloatArray(countsTotal), FloatArray(countsEmitter) );
		}
	}

	// Add total to list.
	mapEffectStats[NULL] = countsTotal;

	// Re-sort by selected stat.
	std::sort(mapEffectStats.begin(), mapEffectStats.end(), SortEffectStats(pSortField));
}

void CParticleManager::GetCounts( SParticleCounts& counts )
{
	FUNCTION_PROFILER_SYS(PARTICLE);

	// Sum all container stats into total.
	ZeroStruct(counts);
	for_all_ptrs (const CParticleEmitter, e, m_Emitters)
	{
		e->GetCounts( counts );
	}
}

void CParticleManager::GetMemoryUsage( ICrySizer* pSizer ) const
{
	{
	  SIZER_COMPONENT_NAME(pSizer, "Effects");
		pSizer->AddObject(m_Effects);
		pSizer->AddObject(m_LoadedLibs);
	}
	{
	  SIZER_COMPONENT_NAME(pSizer, "Emitters");	
		pSizer->AddObject(m_Emitters);		
	}
}

void CParticleManager::PrintParticleMemory()
{
	// Get stats and mem usage for all effects.
	ICrySizer* pSizer = GetSystem()->CreateSizer();
	SEffectCounts eCounts;

	pSizer->AddObject(m_LoadedLibs);
	pSizer->AddObject(m_Effects);

	for_container (TEffectsList, it, m_Effects)
	{
		it->second->GetEffectCounts(eCounts);
	}

	CryLogAlways("Particle effects: %d loaded, %d enabled, %d active, %d KB, %d allocs",
							 eCounts.nLoaded, eCounts.nEnabled, eCounts.nActive, (int)(pSizer->GetTotalSize()>>10), (int)pSizer->GetObjectCount());

	// Get mem usage for all emitters.
	pSizer->Reset();
	pSizer->Add(*this);
	m_Emitters.GetMemoryUsage(pSizer);

	CryLogAlways("Particle emitters: %d KB, %d allocs",
							 (int)(pSizer->GetTotalSize()>>10), (int)pSizer->GetObjectCount());

	stl::SPoolMemoryUsage memEmitter = ParticleAllocator::GetTotalMemory();

	CryLogAlways("Emitter heap: %d KB used, %d KB freed, %d KB unused", 
		(int)(memEmitter.nUsed>>10), (int)(memEmitter.nPoolFree()>>10), (int)(memEmitter.nNonPoolFree()>>10));
	CryLogAlways("Non heap: %d KB", int(pSizer->GetTotalSize() - memEmitter.nUsed)>>10);

	pSizer->Release();
}

void CParticleManager::Reset(bool bIndependentOnly)
{
	// make sure "no mat" and "simple" material are initialized before level starts
	CreateLightShader();

	CParticleManager::Destroy(bIndependentOnly);
}

void CParticleManager::Destroy(bool bIndependentOnly)
{
	m_bRuntime = false;
	m_bRegisteredListener = false;

	for_all_ptrs (CParticleEmitter, e, m_Emitters)
	{
		if (!bIndependentOnly || e->IsIndependent())
		{
			EraseEmitter(e);
		}
	}

	m_PhysEnv.Clear();

	CRY_ASSERT_MESSAGE(m_Emitters.empty() || bIndependentOnly, "CPartManager::Reset failed, the list of emitters should be empty now");
}

//////////////////////////////////////////////////////////////////////////
void CParticleManager::ClearRenderResources( bool bForceClear )
{
	if (GetCVars()->e_ParticlesDebug & AlphaBit('m'))
		PrintParticleMemory();

	Reset(false);

	if (!GetCVars()->e_ParticlesPreload || bForceClear)
	{
		m_Effects.clear();
		m_LoadedLibs.clear();
		m_Emitters.clear();
	}

	CParticleEffect *pEffect = CParticleEffect::get_intrusive_list_root();
	while (pEffect)
	{
		pEffect->UnloadResources(false);
		pEffect = pEffect->m_next_intrusive;
	}
	
	stl::free_container(m_Effects);

	// Since all emitter objects are now freed, we can reset the emitter pools.
	assert(ParticleAllocator::StaticAllocator().GetTotalMemory().nUsed == 0);
	ParticleAllocator::StaticAllocator().FreeMemory(true);
	assert(ParticleAllocator::StaticAllocator().GetTotalMemory().nAlloc == 0);

	m_pPartLightShader = 0;
	m_PhysEnv.FreeMemory();
}

//////////////////////////////////////////////////////////////////////////
void CParticleManager::AddEventListener(IParticleEffectListener *pListener)
{
	assert(pListener);

	if (pListener)
		stl::push_back_unique(m_ListenersList, pListener);
}

void CParticleManager::RemoveEventListener(IParticleEffectListener *pListener)
{
	assert(pListener);

	m_ListenersList.remove(pListener);
}

//////////////////////////////////////////////////////////////////////////
// Particle Effects.
//////////////////////////////////////////////////////////////////////////
IParticleEffect* CParticleManager::CreateEffect()
{
	return new CParticleEffect();
}

//////////////////////////////////////////////////////////////////////////
void CParticleManager::RenameEffect( CParticleEffect* pEffect, cstr sNewName )
{
	assert( pEffect );
	pEffect->AddRef();
	m_Effects.erase( pEffect->GetName() );
	if (*sNewName)
		m_Effects[ sNewName ] = pEffect;
	pEffect->Release();
}

//////////////////////////////////////////////////////////////////////////
void CParticleManager::DeleteEffect( IParticleEffect *pEffect )
{
	assert( pEffect );
	if (!pEffect->GetParent())
		m_Effects.erase( pEffect->GetName() );
}

//////////////////////////////////////////////////////////////////////////
IParticleEffect* CParticleManager::FindEffect( cstr sEffectName, cstr sSource, bool bLoad )
{
	if (!sEffectName || !*sEffectName)
		return NULL;

	LOADING_TIME_PROFILE_SECTION(gEnv->pSystem);

	CParticleEffect* pEffect = FindLoadedEffect(sEffectName);
	if (!pEffect)
	{
		// Try to load the lib.
		cstr sDot = strchr(sEffectName,'.');
		if (sDot)
		{
			stack_string sLibraryName(sEffectName, sDot);
			if (m_bRuntime)
			{
				// Do not load whole library, just requested effect.
				// Inefficient, as entire XML lib is temporarily read, 
				// but runtime loads stall anyway, so we choose saving memory.
				if (m_bRuntime)
				{
					Warning( "Particle effect loaded at runtime: %s", sEffectName );
				}

				XmlNodeRef nodeLib = ReadLibrary(sLibraryName);
				if (nodeLib)
				{
					cstr sBaseName = sDot+1;
					for (int i = nodeLib->getChildCount()-1; i >= 0; i--)
					{
						XmlNodeRef node = nodeLib->getChild(i);
						if (node->isTag("Particles"))
						{
							if (strcmpi(node->getAttr("Name"), sBaseName) == 0)
							{
								return LoadEffect(sEffectName, node, true, sSource);
							}
						}
					}
				}
			}
			else
				LoadLibrary(sLibraryName);

			// Try it again.
			pEffect = FindLoadedEffect(sEffectName);
		}

		if (!pEffect)
		{
			// Not found. Add an empty effect to avoid duplicate loads and warnings.
			pEffect = new CParticleEffect(sEffectName);
			m_Effects[ pEffect->GetName() ] = pEffect;
			Warning("Particle effect not found: '%s'%s%s", sEffectName, *sSource ? " from " : "", sSource);
			return NULL;
		}
	}

	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_Other, 0, "Particles" );
	MEMSTAT_CONTEXT_FMT(EMemStatContextTypes::MSC_ParticleEffect, EMemStatContextFlags::MSF_Instance, "%s", sEffectName);

	assert(pEffect);
	if (pEffect->IsEnabled() || pEffect->GetChildCount())
	{
		if (bLoad && pEffect->LoadResources())
		{
			if (m_bRuntime)
			{
				CryLog("Particle effect loaded at runtime: '%s'%s%s", sEffectName, *sSource ? " from " : "", sSource );
			}
		}
		return pEffect;
	}

	// Empty effect (either disabled params, or cached not found).
	return NULL;
}

//////////////////////////////////////////////////////////////////////////
#ifdef bEVENT_TIMINGS
void CParticleManager::LogEvents()
{
	WriteLock lock(m_EventLock);
	if (GetCVars()->e_ParticlesDebug & AlphaBits('ed'))
	{
		if (!m_aEvents.size())
		{
			// Start timing this frame.
			AddEventTiming("*Frame", 0);
		}
		else
		{
			// Finish and log frame.
			m_aEvents[0].timeEnd = max(GetParticleTimer()->GetAsyncCurTime(), m_aEvents[0].timeStart + GetParticleTimer()->GetRealFrameTime());
			float timeFrameStart = m_aEvents[0].timeStart,
						timeFrameTime = m_aEvents[0].timeEnd - m_aEvents[0].timeStart;

			if (timeFrameTime > 0.f)
			{
				static const int nWidth = 50;
				int nSumStart = m_aEvents.size();
				float timeTotal = 0.f;

				// Add to summary entries at end.
				for (int e = 0; e < nSumStart; e++)
				{
					SEventTiming* pEvent = &m_aEvents[e];
					if (pEvent->nContainerId)
					{
						timeTotal += pEvent->timeEnd - pEvent->timeStart;
						int s;
						for (s = nSumStart; s < m_aEvents.size(); s++)
						{
							SEventTiming* pSum = &m_aEvents[s];
							if (pSum->sEvent == pEvent->sEvent && pSum->nThread == pEvent->nThread)
								break;
						}
						if (s == m_aEvents.size())
						{
							// Add to summary.
							SEventTiming* pSum = m_aEvents.push_back();
							*pSum = m_aEvents[e];
							pSum->nContainerId = 0;
						}
					}
				}

				// Check against time threshold.
				if ((GetCVars()->e_ParticlesDebug & AlphaBit('e')) || timeTotal > m_timeThreshold)
				{
					// Increase threshold for next time.
					m_timeThreshold = timeTotal * 1.1f;

					for_array (c, m_aEvents)
					{
						SEventTiming* pEventC = &m_aEvents[c];
						if (!pEventC->sEvent)
							continue;

						// Iterate unique threads.
						for (int t = c; t < m_aEvents.size(); t++)
						{
							SEventTiming* pEventT = &m_aEvents[t];
							if (!pEventT->sEvent)
								continue;
							if (pEventT->nContainerId != pEventC->nContainerId)
								continue;

							cstr sThread = CryThreadGetName(pEventT->nThread);
							if (pEventT == pEventC)			// Main thread.
								GetLog()->LogToFile( "%*s %s(%X) @%s", 
									nWidth, "", pEventC->pEffect ? pEventC->pEffect->GetName() : "", pEventC->nContainerId, sThread );
							else
								GetLog()->LogToFile( "%*s   @%s", nWidth, "", sThread );

							// Log event times.
							for (int e = t; e < m_aEvents.size(); e++)
							{
								SEventTiming* pEvent = &m_aEvents[e];
								if (!pEvent->sEvent)
									continue;
								if (pEvent->nContainerId != pEventT->nContainerId || pEvent->nThread != pEventT->nThread)
									continue;

								// Construct thread timeline.
								char sGraph[nWidth+1];
								memset(sGraph, ' ', nWidth);
								sGraph[nWidth] = 0;

								int start_iter = pEventC->nContainerId ? e : 0;
								int end_iter = pEventC->nContainerId ? e+1 : nSumStart;
								float timeStart = m_aEvents[e].timeStart, 
											timeEnd = m_aEvents[e].timeEnd,
											timeTotal = 0.f;
								for (int i = start_iter; i < end_iter; i++)
								{
									SEventTiming* pEventI = &m_aEvents[i];
									if (pEventI->sEvent != pEvent->sEvent || pEventI->nThread != pEvent->nThread)
										continue;
 
									timeStart = min(timeStart, pEventI->timeStart);
									timeEnd = max(timeEnd, pEventI->timeEnd);
									timeTotal += pEventI->timeEnd - pEventI->timeStart;

									int nEventStart = int_round((pEventI->timeStart - timeFrameStart) * nWidth / timeFrameTime);
									int nEventEnd = int_round((pEventI->timeEnd - timeFrameStart) * nWidth / timeFrameTime);

									nEventStart = min(nEventStart, nWidth-1);
									nEventEnd = min(max(nEventEnd, nEventStart+1), nWidth);

									cstr sEvent = strrchr(pEventI->sEvent, ':');
									char cEvent = sEvent ? sEvent[1] : *pEventI->sEvent;
									memset(sGraph+nEventStart, cEvent, nEventEnd-nEventStart);
								}

								GetLog()->LogToFile( "%s     %.3f-%.3f [%.3f] %s",
									sGraph,
									(timeStart - timeFrameStart)*1000.f, (timeEnd - timeFrameStart)*1000.f, timeTotal*1000.f,
									pEvent->sEvent );

								pEvent->sEvent = 0;
							}
						}
					}
				}
			}

			// Clear log.
			GetCVars()->e_ParticlesDebug &= ~AlphaBit('e');
			m_aEvents.resize(0);
		}
	}
	else
	{
		m_aEvents.resize(0);
		m_timeThreshold = 0.f;
	}
	m_iEventSwitch *= -1;
}
#endif

void CParticleManager::OnFrameStart()
{
#ifdef bEVENT_TIMINGS
	LogEvents();
#endif

	// Precalc per-frame data.
	UpdateEngineData();
	CreateLightShader();
}

void CParticleManager::Update()
{
	if (!(GetCVars()->e_ParticlesDebug & AlphaBit('z')))
	{	// move all stuff into its own scope to not measure physics time in the fiber
		FUNCTION_PROFILER_CONTAINER(0);
		PARTICLE_LIGHT_PROFILER();

		if (!GetCVars()->e_Particles)
			return;

		// ensure that no spu job from the last frame still runs
		SyncComputeVerticesJobQueues();

		// clear memory used for CREParticle
		gEnv->pRenderer->ClearComputeVerticesQueue();
							
		UpdateParticlesEmitterBatchWrapper spuDispatcher;

		RenderSpuUsage();

		if (GetSystem()->IsPaused() || (GetCVars()->e_ParticlesDebug & AlphaBit('z')))
			return;

		if (!m_bRuntime && !gEnv->IsEditing())
			OnStartRuntime();

		m_bRuntime = !gEnv->IsEditing();

		if (!m_bRegisteredListener && Get3DEngine()->GetIVisAreaManager())
		{
			Get3DEngine()->GetIVisAreaManager()->AddListener(this);
			m_bRegisteredListener = true;
		}

		float fUpdateTime = GetParticleTimer()->GetFrameTime();
	
	
		uint32 nActiveEmitter = 0;

		// Check emitter states.
		for_all_ptrs (CParticleEmitter, e, m_Emitters)
		{		
			// prefetching next emitter
			PrefetchLine(m_Emitters.next(e), 0);
			PrefetchLine(m_Emitters.next(e), 128);
			PrefetchLine(m_Emitters.next(e), 256);
			PrefetchLine(m_Emitters.next(e), 384);
			PrefetchLine(m_Emitters.next(e), 512);				
			
			e->SetUpdateParticlesJobState(NULL);

			e->Update();	
			
			// Cull dead emitters.
			EEmitterState eState = e->GetState();	
			switch (eState)
			{
			case eEmitter_Active:
				if (e->GetEnvFlags() & EFF_ANY)
					e->UpdateEffects();
				if (e->GetEnvFlags() & REN_ANY)
					e->Register(true);
				nActiveEmitter++;
				spuDispatcher.Add(e);				
				break;
			case eEmitter_Dormant:
				if (e->GetEnvFlags() & REN_ANY)
					e->Reset();
				break;
			case eEmitter_Dead:
				EraseEmitter(e);
				break;
			default:
				assert(!"Unhandled EEmitterState");
			}		
		}
		
		m_nActiveEmitter = nActiveEmitter;
	}
}

//////////////////////////////////////////////////////////////////////////
CParticleEmitter* CParticleManager::CreateEmitter( const PivotLocation& loc, const IParticleEffect* pEffect, uint32 uEmitterFlags, const SpawnParams* pSpawnParams )
{
	PARTICLE_LIGHT_PROFILER();
	FUNCTION_PROFILER_SYS(PARTICLE);

	assert(pEffect);

	void* pAlloc;
	if (static_cast<const CParticleEffect*>(pEffect)->GetParams().nEnvFlags & EFF_FORCE)
		// Place emitters that create forces first in the list, so they are updated first.
		pAlloc = m_Emitters.push_front_new();
	else
		pAlloc = m_Emitters.push_back_new();
	if (!pAlloc)
		return NULL;

	CParticleEmitter* pEmitter = new(pAlloc) CParticleEmitter(pEffect, loc, uEmitterFlags, pSpawnParams);
	pEmitter->AddRef();
	pEmitter->Activate(true);

	for_container (TListenersList, itListener, m_ListenersList)
	{
		(*itListener)->OnCreateEmitter(pEmitter, loc, pEffect, uEmitterFlags);
	}		
	return pEmitter;
}

IParticleEmitter* CParticleManager::CreateEmitter( const PivotLocation& loc, const ParticleParams& Params, uint32 uEmitterFlags, const SpawnParams* pSpawnParams )
{
	PARTICLE_LIGHT_PROFILER();
	FUNCTION_PROFILER_SYS(PARTICLE);

	// Create temporary effect for custom params.
	CParticleEffect* pEffect = new CParticleEffect("(Temp)", Params);
	if (!(pEffect->GetParams().nEnvFlags & (REN_ANY | EFF_ANY)))
	{
		// Assume custom params with no specified texture or geometry can have geometry particles added.
		pEffect->SetEnvironFlags(REN_GEOMETRY);
	}

	IParticleEmitter* pEmitter = CreateEmitter( loc, pEffect, uEmitterFlags | ePEF_TemporaryEffect, pSpawnParams );

	if (!pEmitter)
	{
		// If the emitter failed to be allocated, the effect will leak (as it won't be bound to the emitter). Free it.
		pEffect->Release();
	}

	return pEmitter;
}

//////////////////////////////////////////////////////////////////////////
void CParticleManager::EraseEmitter( CParticleEmitter *pEmitter )
{
	if (pEmitter)
	{
		// Mark emitter dead, free resources.
		pEmitter->Kill();
		if (pEmitter->GetRefCount() == 1)
		{
			// Free object itself if no other refs.
			m_Emitters.erase(pEmitter);
		}
	}
}

void CParticleManager::OnDeleteEmitter( IParticleEmitter *pEmitter )
{
	// Mark emitter as dead, without yet freeing resources.
	for_container (TListenersList, itListener, m_ListenersList)
	{
		(*itListener)->OnDeleteEmitter(pEmitter);
	}
}

void CParticleManager::DeleteEmitters( uint32 mask )
{
	for_all_ptrs (CParticleEmitter, e, m_Emitters)
	{
		if (e->GetEmitterFlags() & mask)
		{
			e->Kill();
		}
	}
}

void CParticleManager::UpdateEmitters( IParticleEffect *pEffect )
{
	// Update all emitters with this effect tree.
	for_all_ptrs (CParticleEmitter, e, m_Emitters)
	{
		for (IParticleEffect* pTest = pEffect; pTest; pTest = pTest->GetParent())
			if (e->GetEffect() == pTest)
			{
				e->SetEffect(pTest);
				break;
			}
	}
}

void CParticleManager::UpdateEngineData()
{
	// Set to reasonable default value: 1 pix particles, 720 lines.
	m_nAllowedEnvironmentFlags = ~0;

	// Update allowed particle features.
	if (GetCVars()->e_ParticlesObjectCollisions < 2)
	{
		m_nAllowedEnvironmentFlags &= ~ENV_DYNAMIC_ENT;
		if (GetCVars()->e_ParticlesObjectCollisions < 1)
			m_nAllowedEnvironmentFlags &= ~ENV_STATIC_ENT;
	}
	if (!GetCVars()->e_ParticlesLights || !GetCVars()->e_DynamicLights)
		m_nAllowedEnvironmentFlags &= ~REN_LIGHTS;

	// Render object flags, using cvar convention of 1 = allowed, 2 = forced.
	m_RenderFlags.Clear();
	m_RenderFlags.SetState( GetCVars()->e_ParticlesAnimBlend - 1, OS_ANIM_BLEND );
	m_RenderFlags.SetState( GetCVars()->e_ParticlesDiffCM - 1, OS_DIFFUSE_CUBEMAP);
	m_RenderFlags.SetState( GetCVars()->e_ParticlesMotionBlur - 1, FOB_MOTION_BLUR_PARTICLE);
	m_RenderFlags.SetState( GetCVars()->e_ParticlesShadows - 1, FOB_INSHADOW);
	m_RenderFlags.SetState( GetCVars()->e_ParticlesGI - 1, FOB_GLOBAL_ILLUMINATION);
	m_RenderFlags.SetState( GetCVars()->e_ParticlesSoftIntersect - 1, FOB_SOFT_PARTICLE);
}

void CParticleManager::OnVisAreaDeleted( IVisArea* pVisArea )
{
	for_all_ptrs (CParticleEmitter, e, m_Emitters)
		e->OnVisAreaDeleted(pVisArea);
}

IParticleEmitter* CParticleManager::SerializeEmitter( TSerialize ser, IParticleEmitter* pIEmitter )
{
	ser.BeginGroup("Emitter");

	string sEffect;
	QuatTS qLoc;

	CParticleEmitter* pEmitter = static_cast<CParticleEmitter*>(pIEmitter);
	if (pEmitter)
	{
		if (const IParticleEffect* pEffect = pEmitter->GetEffect())
			sEffect = pEffect->GetName();
		qLoc = pEmitter->GetLocation();
	}

	ser.Value("Effect", sEffect);

	ser.Value("Pos", qLoc.t);
	ser.Value("Rot", qLoc.q);
	ser.Value("Scale", qLoc.s);

	if (ser.IsReading())
	{
		assert(!pEmitter);
		if (IParticleEffect* pEffect = FindEffect(sEffect))
			pEmitter = CreateEmitter(qLoc, pEffect);
	}

	if (pEmitter)
	{
		pEmitter->SerializeState(ser);
	}

	ser.EndGroup();

	return pEmitter;
}


//////////////////////////////////////////////////////////////////////////
class CLibPathIterator
{
public:

	CLibPathIterator(cstr sLevelPath = "")
		: sPath(*sLevelPath ? sLevelPath : LEVEL_PATH), bDone(false)
		{}
	operator bool() const
		{ return !bDone; }
	const string& operator *() const
		{ return sPath; }

	void operator++()
	{
		if (sPath.empty())
			bDone = true;
		else
			sPath = PathUtil::GetParentDirectory(sPath);
	}

protected:
	string sPath;
	bool bDone;
};

//////////////////////////////////////////////////////////////////////////
bool CParticleManager::LoadPreloadLibList(const cstr filename, const bool bLoadResources)
{
	int nCount = 0;
	CCryFile file;
	if (file.Open(filename, "r"))
	{
		const size_t nLen = file.GetLength();
		string sAllText;
		if (nLen > 0)
		{
			std::vector<char> buffer;
			buffer.resize(nLen, '\n');
			file.ReadRaw(&buffer[0], nLen);
			sAllText.assign(&buffer[0], nLen);
		}

		int pos = 0;
		string sLine;
		while (!(sLine = sAllText.Tokenize("\r\n", pos)).empty())
		{
			if (LoadLibrary( sLine, NULL, bLoadResources ))
			{
				nCount++;
			}
		}		
	}
	return nCount > 0;
}

//////////////////////////////////////////////////////////////////////////
bool CParticleManager::LoadLibrary( cstr sParticlesLibrary, cstr sParticlesLibraryFile, bool bLoadResources )
{
	if (strchr(sParticlesLibrary, '*'))
	{
		// Wildcard load.
		if (!sParticlesLibraryFile)
		{
			// Load libs from level-local and global paths.
			int nCount = 0;
			for (CLibPathIterator path(Get3DEngine()->GetLevelFilePath("")); path; ++path)
				nCount += LoadLibrary(sParticlesLibrary, PathUtil::Make(*path, EFFECTS_SUBPATH), bLoadResources);
			return nCount > 0;
		}
		else
		{
			// Load from specified path.
			string sLibPath = PathUtil::Make( sParticlesLibraryFile, sParticlesLibrary, "xml" );
			ICryPak *pack = gEnv->pCryPak;
			_finddata_t fd;
			intptr_t handle = pack->FindFirst( sLibPath, &fd );
			int nCount = 0;
			if (handle >= 0)
			{
				do {
					if (LoadLibrary( PathUtil::GetFileName(fd.name), PathUtil::Make( sParticlesLibraryFile, fd.name), bLoadResources ))
						nCount++;
				} while (pack->FindNext( handle, &fd ) >= 0);
				pack->FindClose(handle);
			}
			return nCount > 0;
		}
	}
	else if (sParticlesLibrary[0] == '@')
	{
		// first we try to load the level specific levelSpecificFile - if it exists, the libraries it lists are added
		// to those from the default levelSpecificFile
		string sFilename = PathUtil::Make(sParticlesLibraryFile, sParticlesLibrary+1, "txt");
		bool levelSpecificLoaded = LoadPreloadLibList(sFilename, bLoadResources);

		// also load the default package
		sFilename = PathUtil::Make(EFFECTS_SUBPATH, sParticlesLibrary+1, "txt");
		bool globalFileLoaded = LoadPreloadLibList(sFilename, bLoadResources);
		return globalFileLoaded || levelSpecificLoaded;
	}

	if (m_LoadedLibs[sParticlesLibrary])
	{
		// Already loaded.
		if (bLoadResources)
		{
			if (m_bRuntime)
			{
				Warning( "Particle effects loaded at runtime: %s", sParticlesLibrary );
			}

			// Iterate all fx in lib and load their resources.
			stack_string sPrefix = sParticlesLibrary;
			sPrefix += ".";
			for_container (TEffectsList, it, m_Effects)
			{
				CParticleEffect* pEffect = it->second;
				if (pEffect && strnicmp(pEffect->GetName(), sPrefix, sPrefix.size()) == 0)
				{
					pEffect->LoadResources(true);
				}
			}
		}
	}
	else
	{
		if (m_bRuntime)
		{
			Warning( "Particle library loaded at runtime: %s", sParticlesLibrary );
		}

		XmlNodeRef libNode;
		if (sParticlesLibraryFile)
		{
			// Load from specified location.
			libNode = GetISystem()->LoadXmlFromFile( sParticlesLibraryFile );
		}
		else
		{
			libNode = ReadLibrary( sParticlesLibrary );
		}

		if (!libNode)
		{
			return false;
		}

		LoadLibrary(sParticlesLibrary, libNode, bLoadResources);
	}
	return true;
}

XmlNodeRef CParticleManager::ReadLibrary( cstr sParticlesLibrary )
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_Other, 0, "ParticleLibraries" );
	MEMSTAT_CONTEXT_FMT(EMemStatContextTypes::MSC_ParticleLibrary, 0, "Particle lib (%s)", sParticlesLibrary);

	string sLibSubPath = PathUtil::Make( EFFECTS_SUBPATH, sParticlesLibrary, "xml" );
	if (GetCVars()->e_ParticlesUseLevelSpecificLibs)
	{
		// Look for library in level-specific, then general locations.
		for (CLibPathIterator path(Get3DEngine()->GetLevelFilePath("")); path; ++path)
		{
			XmlNodeRef libNode = GetISystem()->LoadXmlFromFile( PathUtil::Make(*path, sLibSubPath) );
			if (libNode)
			{
				return libNode;
			}
		}
	}
	else
	{
		XmlNodeRef libNode = GetISystem()->LoadXmlFromFile(sLibSubPath);
		if (libNode)
		{
			return libNode;
		}
	}
	return NULL;
}

bool CParticleManager::LoadLibrary(cstr sParticlesLibrary, XmlNodeRef& libNode, bool bLoadResources )
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_Other, 0, "ParticleLibraries" );
	MEMSTAT_CONTEXT_FMT(EMemStatContextTypes::MSC_ParticleLibrary, 0, "Particle lib (%s)", sParticlesLibrary);

	CRY_DEFINE_ASSET_SCOPE( "ParticleLibrary",sParticlesLibrary );

	m_LoadedLibs[sParticlesLibrary] = libNode;

	// Load all nodes from the particle libaray.
	for (int i = 0, count = libNode->getChildCount(); i < count; i++)
	{
		XmlNodeRef effectNode = libNode->getChild(i);
		if (effectNode->isTag("Particles"))
		{
			string sEffectName = sParticlesLibrary;
			sEffectName += ".";
			sEffectName += effectNode->getAttr("Name");

			// Load the full effect.
			LoadEffect( sEffectName, effectNode, bLoadResources );
		}
	}
	return true;
}

IParticleEffect* CParticleManager::LoadEffect(cstr sEffectName, XmlNodeRef& effectNode, bool bLoadResources, const cstr sSource)
{
	MEMSTAT_CONTEXT_FMT(EMemStatContextTypes::MSC_ParticleEffect, 0, "%s", sEffectName);

	CParticleEffect* pEffect;
	TEffectsList::iterator it = m_Effects.find( sEffectName );
	if (it != m_Effects.end())
		pEffect = it->second;
	else
	{
		pEffect = new CParticleEffect(sEffectName);
		m_Effects[ pEffect->GetName() ] = pEffect;
	}

	// Load effect params and all assets.
	pEffect->Serialize( effectNode, true, true );
	if (bLoadResources)
	{
		if (pEffect->LoadResources() && m_bRuntime)
		{
			CryLog("Particle effect loaded at runtime: '%s' from %s",
				sEffectName, sSource ? sSource : "<unspecified source>");
		}
	}

	return pEffect;
}

void CParticleManager::OnStartRuntime()
{
	ClearCachedLibraries();
}

void CParticleManager::ClearCachedLibraries()
{
	if (GetCVars()->e_ParticlesDebug & AlphaBit('m'))
		PrintParticleMemory();

	for (TEffectsList::iterator it = m_Effects.begin(); it != m_Effects.end(); )
	{
		// Purge all unused effects.
		if (!it->second->ResourcesLoaded(true))
			it = m_Effects.erase(it);
		else
			++it; 
	}
	m_LoadedLibs.clear();

	if ((GetCVars()->e_ParticlesDebug & AlphaBit('m')) || GetCVars()->e_ParticlesDumpMemoryAfterMapLoad)
		PrintParticleMemory();
}

void CParticleManager::RenderDebugInfo()
{
  if ((GetCVars()->e_ParticlesDebug & AlphaBit('b')) || gEnv->IsEditing())
  {
		// Debug particle BBs.
	  FUNCTION_PROFILER_SYS(PARTICLE);
		for_all_ptrs (CParticleEmitter, e, m_Emitters)
			if (!(e->GetEmitterFlags() & ePEF_Nowhere))
				e->RenderDebugInfo();
	}
}


void CParticleManager::RenderSpuUsage()
{		




















}

#ifdef bEVENT_TIMINGS

int CParticleManager::AddEventTiming( cstr sEvent, const CParticleContainer* pCont )
{
	if (!m_aEvents.size() && *sEvent != '*')
		return -1;

	SEventTiming* pEvent = m_aEvents.push_back();
	pEvent->nContainerId = (uint32)pCont;
	pEvent->pEffect = pCont ? pCont->GetEffect() : 0;
	pEvent->nThread = CryGetCurrentThreadId();
	pEvent->sEvent = sEvent;
	pEvent->timeStart = GetParticleTimer()->GetAsyncCurTime();

	return m_aEvents.size()-1;
}

#endif

//////////////////////////////////////////////////////////////////////////
void CParticleManager::Serialize(TSerialize ser)
{
	ser.BeginGroup("ParticleEmitters");

	if (ser.IsWriting())
	{
		ListEmitters("before save");

		int nCount = 0;

		for_all_ptrs (CParticleEmitter, e, m_Emitters)
		{
			if (e->NeedSerialize())
			{
				SerializeEmitter(ser, e);
				++nCount;
			}
		}
		ser.Value("Emitters", nCount);
	}
	else
	{
		ListEmitters("before load");
		m_bRuntime = false;

		// Clean up existing emitters.
		for_all_ptrs (CParticleEmitter, e, m_Emitters)
		{
			if (e->IsIndependent())
			{
				// Would be serialized.
				EraseEmitter(e);
			}
			else if (!e->IsAlive())
			{
				// No longer exists at new time.
				EraseEmitter(e);
			}
		}

		int nCount = 0;
		ser.Value("Emitters", nCount);
		while (nCount-- > 0)
		{
			SerializeEmitter(ser);
		}
	}
	ser.EndGroup();
}

void CParticleManager::PostSerialize( bool bReading )
{
	if (bReading)
		ListEmitters("after load");
}

void CParticleManager::ListEmitters( cstr sDesc, bool bForce )
{
	if (bForce || GetCVars()->e_ParticlesDebug & AlphaBit('l'))
	{
		// Count emitters.
		int anEmitters[eEmitter_Active+1] = {0};

		// Log summary, and state of each emitter.
		CryLog( "Emitters %s: time %.3f", sDesc, GetParticleTimer()->GetFrameStartTime().GetSeconds() );
		for_all_ptrs (const CParticleEmitter, e, m_Emitters)
		{
			EEmitterState eState = e->GetState();
			anEmitters[eState]++;
			CryLog( " %s", e->GetDebugString('s').c_str() );
		}
		CryLog( "Total: %d active, %d dormant, %d dead", 
			anEmitters[eEmitter_Active], anEmitters[eEmitter_Dormant], anEmitters[eEmitter_Dead] );
	}
}

void CParticleManager::ListEffects()
{
	// Collect all container stats, sum into effects map.
	TEffectStats mapEffectStats;
	CollectEffectStats( mapEffectStats, &SParticleCounts::ParticlesActive );

	// Header for CSV-formatted effect list.
	CryLogAlways(
		"Effect, "
		"Ems ren, Ems act, Ems all, "
		"Parts ren, Parts act, Parts all, "
		"Fill ren, Fill act, "
		"Stat vol, Dyn vol, Err vol,"
		"Coll test, Coll hit, Clip, "
		"Reiter, Reject"
	);

	for_container (CParticleManager::TEffectStats, it, mapEffectStats)
	{
		SParticleCounts const& counts = it->second;
		float fPixToScreen = 1.f / ((float)GetRenderer()->GetWidth() * (float)GetRenderer()->GetHeight());
		CryLogAlways(
			"%s, "
			"%.0f, %.0f, %.0f, "
			"%.0f, %.0f, %.0f, "
			"%.3f, %.3f, "
			"%.0f, %.0f, %.2f, "
			"%.2f, %.2f, %.2f, "
			"%.0f, %.2f",
			it->first ? (cstr)it->first->GetFullName() : "TOTAL", 
			counts.EmittersRendered, counts.EmittersActive, counts.EmittersAlloc,
			counts.ParticlesRendered, counts.ParticlesActive, counts.ParticlesAlloc, 
			counts.PixelsRendered * fPixToScreen, counts.PixelsProcessed * fPixToScreen,
			counts.StaticBoundsVolume, counts.DynamicBoundsVolume, counts.ErrorBoundsVolume,
			counts.ParticlesCollideTest, counts.ParticlesCollideHit, counts.ParticlesClip,
			counts.ParticlesReiterate, counts.ParticlesReject
		);
	}
}

void CParticleManager::CreatePerfHUDWidget()
{
	if (!m_pWidget)
	{
		if (ICryPerfHUD* pPerfHUD = gEnv->pSystem->GetPerfHUD())
		{
			if (IMiniCtrl* pRenderMenu = pPerfHUD->GetMenu("Rendering"))
			{
				m_pWidget = new CParticleWidget(pRenderMenu, pPerfHUD, this);
			}
		}
	}
}

void CParticleManager::FinishRender()
{
	// Sync particle update tasks
	for_all_ptrs (JobManager::SJobState, pJob, m_UpdateParticleStates)
		gEnv->GetJobManager()->WaitForJob(*pJob);		
	for_all_ptrs (CParticleEmitter, e, m_Emitters)
		e->SetUpdateParticlesJobState(NULL);
	m_UpdateParticleStates.clear();
}


void CParticleManager::SyncComputeVerticesJobQueues()
{
	// forward call to free function
	::SyncComputeVerticesJobQueues();
}

//PerfHUD
CParticleWidget::CParticleWidget(IMiniCtrl* pParentMenu, ICryPerfHUD *pPerfHud, CParticleManager *pPartManager) : ICryPerfHUDWidget(eWidget_Particles)
{
	m_pPartMgr = pPartManager;

	m_pTable = pPerfHud->CreateTableMenuItem(pParentMenu, "Particles");

	pPerfHud->AddWidget(this);

	m_displayMode = PARTICLE_DISP_MODE_NONE;
}

CParticleWidget::~CParticleWidget()
{
}

void CParticleWidget::Enable(int mode)
{
	mode = min(mode, PARTICLE_DISP_MODE_NUM-1);
	EPerfHUD_ParticleDisplayMode newMode = (EPerfHUD_ParticleDisplayMode)mode;

	if (m_displayMode != newMode)
	{
		m_pTable->RemoveColumns();
		m_pTable->AddColumn("Effect Name");

		switch (newMode)
		{
		case PARTICLE_DISP_MODE_PARTICLE:
		case PARTICLE_DISP_MODE_FILL:
			m_pTable->AddColumn("P. Rendered");
			m_pTable->AddColumn("P. Active");
			m_pTable->AddColumn("P. Alloc");
			m_pTable->AddColumn("P. Fill");
			m_displayMode = newMode;
			break;

		case PARTICLE_DISP_MODE_EMITTER:
			m_pTable->AddColumn("E. Rendered");
			m_pTable->AddColumn("E. Active");
			m_pTable->AddColumn("E. Alloc");
			m_displayMode = newMode;
			break;

		default:
			CryLogAlways("[Particle Stats] Attempting to set incorrect display mode set: %d", mode);
			break;
		}
	}

	m_pTable->Hide(false); 
}


bool CParticleWidget::ShouldUpdate()
{
	return !m_pTable->IsHidden();
}
	
void CParticleWidget::Update()
{
  FUNCTION_PROFILER_SYS(PARTICLE);

	m_pTable->ClearTable();

	ColorB col(255,255,255,255);
		
	CParticleManager::TEffectStats mapEffectStats;

	switch (m_displayMode)
	{
	case PARTICLE_DISP_MODE_PARTICLE:
		m_pPartMgr->CollectEffectStats( mapEffectStats, &SParticleCounts::ParticlesRendered );
		break;

	case PARTICLE_DISP_MODE_FILL:
		m_pPartMgr->CollectEffectStats( mapEffectStats, &SParticleCounts::PixelsRendered );
		break;

	case PARTICLE_DISP_MODE_EMITTER:
		m_pPartMgr->CollectEffectStats( mapEffectStats, &SParticleCounts::EmittersRendered );
		break;
	}

	if (mapEffectStats[NULL].ParticlesAlloc)
	{
		float fPixToScreen = 1.f / (gEnv->pRenderer->GetWidth() * gEnv->pRenderer->GetHeight());
		for_container (CParticleManager::TEffectStats, it, mapEffectStats)
		{
			m_pTable->AddData(0, col, "%s", it->first ? (cstr)it->first->GetFullName() : "TOTAL");

			SParticleCounts const& counts = it->second;
			if (m_displayMode == PARTICLE_DISP_MODE_EMITTER)
			{
				m_pTable->AddData(1, col, "%d", (int)counts.EmittersRendered);
				m_pTable->AddData(2, col, "%d", (int)counts.EmittersActive);
				m_pTable->AddData(3, col, "%d", (int)counts.EmittersAlloc);
			}
			else
			{
				m_pTable->AddData(1, col, "%d", (int)counts.ParticlesRendered);
				m_pTable->AddData(2, col, "%d", (int)counts.ParticlesActive);
				m_pTable->AddData(3, col, "%d", (int)counts.ParticlesAlloc);
				m_pTable->AddData(4, col, "%.2f", counts.PixelsRendered * fPixToScreen);
			}
		}
	}
}

CParticleManager::SEffectsKey::SEffectsKey( const cstr &sName )
{
	stack_string lowerName(sName);
	CryStringUtils::toLowerInplace( (char*)lowerName.c_str());			
	MD5Context context;
	MD5Init(&context);
	MD5Update(&context, (unsigned char*)lowerName.c_str(), lowerName.size() );
	MD5Final(c16, &context);
}

